#pragma once
#include <queue>
#include <BWAPI.h>
#include "ConcurrecyQueue.h"

using namespace std;
using namespace BWAPI;

class UnitGroup;
class BasicManager;

class BasicAction
{
	friend class UnitGroup;
	bool stopped;
	int order;
protected:
	int priority;
	UnitGroup *group;
	BasicManager *manager;
	virtual void _onFrame() = 0;
	virtual void _onActionStart() = 0;
	virtual void _onActionStop() = 0;
public:
	BasicAction();
	virtual BasicAction*clone() = 0;
	void think();
	void start();
	void stop();
	inline void setGroup(UnitGroup *newGroup) { group = newGroup; }
	inline void setManager(BasicManager *newManager) { manager = newManager; }
	inline BasicManager* getManager() { return manager; }
	inline int getPriority() { return priority; }

	inline void setOrder(int o) { order = o; }
	inline int getOrder() { return order; }
	inline bool isStopped() { return stopped; }
};

// This class compares linear actions to make sure they appear
// in the proper order. ie lowest order is first.
class ActionCompare
{
public:
	bool operator() (BasicAction* lhs, BasicAction* rhs)
	{
		return(lhs->getOrder() > rhs->getOrder());
	}
};

// This class compares concurrent actions to make sure they
// recieve a think command in the proper prioritized order.
// It also makes sure a regular push to a concurrency queue
// places the action in the proper prioritized thread
class ConcurrentCompare
{
public:
	bool operator()(BasicAction* lhs, BasicAction* rhs)
    {
		return (lhs->getPriority() < rhs->getPriority());
	}
};

class UnitGroup
{
	Unitset units;
	//BasicAction *action;
	ConcurrencyQueue<BasicAction*, ActionCompare, ConcurrentCompare> *actionsQueue;
	std::set<BasicManager*> managers;
	//void setAction(BasicAction *newAction);
	void nextAction(int level);
	bool enabled;
public:
	UnitGroup(int actionSize = 5);
	UnitGroup(Unitset unitSet, int actionSize = 5);
	void think();
	void stop() { enabled = false; }
	void addUnit(Unit *unit);
	Unitset* getUnits();
	void addAction(BasicAction *newAction, BasicManager *manager, int priority, int order = 0);
	void onActionCompleted(BasicAction* action);
	void onTaskCompleted();
	int getCount();
	~UnitGroup();
};

class PUnitType : public UnitType
{
	UnitGroup *initialGroup;
	int priority;
	TilePosition position;
public:
	PUnitType(UnitType& uType, UnitGroup *group, int priority=0, TilePosition pos=TilePositions::None)
	{
		initialGroup = group;
		id = uType.getID();
		this->priority = priority;
		position = pos;
	}
	inline int getPriority() { return priority; }
	inline TilePosition getTilePosition() { return position; }
	inline UnitGroup* getGroup() { return initialGroup; }
};

struct arrival
{
	int arrivalTime;
	Unit *origin;
	UnitGroup *group;
	UnitType uType;
};

class ArrivalCompare
{
public:
	bool operator() (struct arrival& lhs, struct arrival& rhs)
	{
		return lhs.arrivalTime < rhs.arrivalTime;
	}
};

